import { VantComponent } from "../common/component";
import { touch } from "../mixins/touch";
import { getAllRect, getRect, groupSetData, nextTick, requestAnimationFrame } from "../common/utils";
import { isDef } from "../common/validator";
import { useChildren } from "../common/relation";
VantComponent({
	mixins: [touch],
	classes: ["nav-class", "tab-class", "tab-active-class", "line-class", "wrap-class"],
	relation: useChildren("tab", function () {
		this.updateTabs();
	}),
	props: {
		sticky: Boolean,
		border: Boolean,
		swipeable: Boolean,
		titleActiveColor: String,
		titleInactiveColor: String,
		color: String,
		animated: {
			type: Boolean,
			observer() {
				this.children.forEach((child, index) => child.updateRender(index === this.data.currentIndex, this));
			}
		},
		lineWidth: {
			type: null,
			value: 40,
			observer: "resize"
		},
		lineHeight: {
			type: null,
			value: -1
		},
		active: {
			type: null,
			value: 0,
			observer(name) {
				if (name !== this.getCurrentName()) {
					this.setCurrentIndexByName(name);
				}
			}
		},
		type: {
			type: String,
			value: "line"
		},
		ellipsis: {
			type: Boolean,
			value: true
		},
		duration: {
			type: Number,
			value: 0.3
		},
		zIndex: {
			type: Number,
			value: 1
		},
		swipeThreshold: {
			type: Number,
			value: 5,
			observer(value) {
				this.setData({
					scrollable: this.children.length > value || !this.data.ellipsis
				});
			}
		},
		offsetTop: {
			type: Number,
			value: 0
		},
		lazyRender: {
			type: Boolean,
			value: true
		},
		useBeforeChange: {
			type: Boolean,
			value: false
		}
	},
	data: {
		tabs: [],
		scrollLeft: 0,
		scrollable: false,
		currentIndex: 0,
		container: null,
		skipTransition: true,
		scrollWithAnimation: false,
		lineOffsetLeft: 0,
		inited: false
	},
	mounted() {
		requestAnimationFrame(() => {
			this.swiping = true;
			this.setData({
				container: () => this.createSelectorQuery().select(".van-tabs")
			});
			this.resize();
			this.scrollIntoView();
		});
	},
	methods: {
		updateTabs() {
			const { children = [], data } = this;
			this.setData({
				tabs: children.map(child => child.data),
				scrollable: this.children.length > data.swipeThreshold || !data.ellipsis
			});
			this.setCurrentIndexByName(data.active || this.getCurrentName());
		},
		trigger(eventName, child) {
			const { currentIndex } = this.data;
			const data = this.getChildData(currentIndex, child);
			if (!isDef(data)) {
				return;
			}
			this.$emit(eventName, data);
		},
		onTap(event) {
			const { index } = event.currentTarget.dataset;
			const child = this.children[index];
			if (child.data.disabled) {
				this.trigger("disabled", child);
				return;
			}
			this.onBeforeChange(index).then(() => {
				this.setCurrentIndex(index);
				nextTick(() => {
					this.trigger("click");
				});
			});
		},
		// correct the index of active tab
		setCurrentIndexByName(name) {
			const { children = [] } = this;
			const matched = children.filter(child => child.getComputedName() === name);
			if (matched.length) {
				this.setCurrentIndex(matched[0].index);
			}
		},
		setCurrentIndex(currentIndex) {
			const { data, children = [] } = this;
			if (!isDef(currentIndex) || currentIndex >= children.length || currentIndex < 0) {
				return;
			}
			groupSetData(this, () => {
				children.forEach((item, index) => {
					const active = index === currentIndex;
					if (active !== item.data.active || !item.inited) {
						item.updateRender(active, this);
					}
				});
			});
			if (currentIndex === data.currentIndex) {
				if (!data.inited) {
					this.resize();
				}
				return;
			}
			const shouldEmitChange = data.currentIndex !== null;
			this.setData({ currentIndex });
			requestAnimationFrame(() => {
				this.resize();
				this.scrollIntoView();
			});
			nextTick(() => {
				this.trigger("input");
				if (shouldEmitChange) {
					this.trigger("change");
				}
			});
		},
		getCurrentName() {
			const activeTab = this.children[this.data.currentIndex];
			if (activeTab) {
				return activeTab.getComputedName();
			}
		},
		resize() {
			if (this.data.type !== "line") {
				return;
			}
			const { currentIndex, ellipsis, skipTransition } = this.data;
			Promise.all([getAllRect(this, ".van-tab"), getRect(this, ".van-tabs__line")]).then(
				([rects = [], lineRect]) => {
					const rect = rects[currentIndex];
					if (rect == null) {
						return;
					}
					let lineOffsetLeft = rects.slice(0, currentIndex).reduce((prev, curr) => prev + curr.width, 0);
					lineOffsetLeft += (rect.width - lineRect.width) / 2 + (ellipsis ? 0 : 8);
					this.setData({ lineOffsetLeft, inited: true });
					this.swiping = true;
					if (skipTransition) {
						// waiting transition end
						setTimeout(() => {
							this.setData({ skipTransition: false });
						}, this.data.duration);
					}
				}
			);
		},
		// scroll active tab into view
		scrollIntoView() {
			const { currentIndex, scrollable, scrollWithAnimation } = this.data;
			if (!scrollable) {
				return;
			}
			Promise.all([getAllRect(this, ".van-tab"), getRect(this, ".van-tabs__nav")]).then(([tabRects, navRect]) => {
				const tabRect = tabRects[currentIndex];
				const offsetLeft = tabRects.slice(0, currentIndex).reduce((prev, curr) => prev + curr.width, 0);
				this.setData({
					scrollLeft: offsetLeft - (navRect.width - tabRect.width) / 2
				});
				if (!scrollWithAnimation) {
					nextTick(() => {
						this.setData({ scrollWithAnimation: true });
					});
				}
			});
		},
		onTouchScroll(event) {
			this.$emit("scroll", event.detail);
		},
		onTouchStart(event) {
			if (!this.data.swipeable) return;
			this.swiping = true;
			this.touchStart(event);
		},
		onTouchMove(event) {
			if (!this.data.swipeable || !this.swiping) return;
			this.touchMove(event);
		},
		// watch swipe touch end
		onTouchEnd() {
			if (!this.data.swipeable || !this.swiping) return;
			const { direction, deltaX, offsetX } = this;
			const minSwipeDistance = 50;
			if (direction === "horizontal" && offsetX >= minSwipeDistance) {
				const index = this.getAvaiableTab(deltaX);
				if (index !== -1) {
					this.onBeforeChange(index).then(() => this.setCurrentIndex(index));
				}
			}
			this.swiping = false;
		},
		getAvaiableTab(direction) {
			const { tabs, currentIndex } = this.data;
			const step = direction > 0 ? -1 : 1;
			for (let i = step; currentIndex + i < tabs.length && currentIndex + i >= 0; i += step) {
				const index = currentIndex + i;
				if (index >= 0 && index < tabs.length && tabs[index] && !tabs[index].disabled) {
					return index;
				}
			}
			return -1;
		},
		onBeforeChange(index) {
			const { useBeforeChange } = this.data;
			if (!useBeforeChange) {
				return Promise.resolve();
			}
			return new Promise((resolve, reject) => {
				this.$emit(
					"before-change",
					Object.assign(Object.assign({}, this.getChildData(index)), {
						callback: status => (status ? resolve() : reject())
					})
				);
			});
		},
		getChildData(index, child) {
			const currentChild = child || this.children[index];
			if (!isDef(currentChild)) {
				return;
			}
			return {
				index: currentChild.index,
				name: currentChild.getComputedName(),
				title: currentChild.data.title
			};
		}
	}
});
